# 交易执行类
# from multiprocessing import Queue, Process, Lock
from threading import Thread, Lock
from queue import Queue


class ITrader(Thread):
    trader_queue = Queue()
    public_context = None
    strategy_manager = None
    portfolio = {}
    portfolio_lock = Lock()
    system_order_list = {}
    cur_system_order_id = 1
    sys_order_id_lock = Lock()

    def __init__(self):
        super().__init__()
        self.trader_queue = Queue()
        self.public_context = None
        self.strategy_manager = None
        self.portfolio = {}
        self.portfolio_lock = Lock()
        self.system_order_list = {}
        self.cur_system_order_id = 1
        self.sys_order_id_lock = Lock()



    def initialize(self, init_portfolio):
        with self.portfolio_lock:
            self.portfolio = init_portfolio

    # 通过不同的execute_order函数实现不同的功能
    def execute_order(self, order):
        pass

    # 向trader_queue放事件
    def put_event(self, evt_type, data):
        self.trader_queue.put({'type': evt_type, 'data': data})

    # 向Trader队列投放订单
    def send_order(self, order):
        # 首先判断订单是否合格
        if (order.order_type != 'market_order' and order.order_type != 'limit_order') or \
                (order.direction != 'BUY' and order.direction != 'SELL'):
            self.public_context.log_message('TRADER - INVALID ORDER: wrong type or direction for {}'.format(str(order)))
            return False
        # 首先判断订单是否有充足资金/仓位，若有，扣减相应数额的现金和仓位
        cur_strategy = order.strategy_name
        cur_inst_id = order.inst_id
        with self.portfolio_lock:
            if order.direction == 'BUY':
                if self.portfolio[cur_strategy]['cash'] > order.volume * order.price:  # 不应把钱花光
                    self.portfolio[cur_strategy]['cash'] -= order.volume * order.price
                else:
                    self.public_context.log_message('TRADER - INVALID ORDER: no enough cash for {}'.format(str(order)))
                    return False
            elif order.direction == 'SELL':
                if cur_inst_id in self.portfolio[cur_strategy].keys() and \
                        self.portfolio[cur_strategy][cur_inst_id]['sellable_position'] >= order.volume:
                    self.portfolio[cur_strategy][cur_inst_id]['sellable_position'] -= order.volume
                    self.portfolio[cur_strategy][cur_inst_id]['locked_position'] += order.volume
                else:
                    self.public_context.log_message('TRADER - INVALID ORDER: no enough position for {}'.format(str(order)))
                    return False
        # 若通过了订单校验：
        # 设置系统order ID
        with self.sys_order_id_lock:
            order.system_order_id = 'SYS_ORDER_{}'.format(str(self.cur_system_order_id))
            self.cur_system_order_id += 1
        # 登记新订单
        self.system_order_list[order.system_order_id] = order
        self.put_event('order', order)
        # 下单完成，返回True
        return True

    # 处理成交回报
    def process_order_update(self, order_update):
        self.public_context.log_message('Execute order update {}'.format(order_update))
        cur_order_id = order_update.system_order_id
        cur_inst_id = self.system_order_list[cur_order_id].inst_id
        cur_strategy = self.system_order_list[cur_order_id].strategy_name
        # 对于成交回报
        if order_update.update_type == 'DEAL':
            # 先减少订单中剩余的部分
            self.system_order_list[cur_order_id].volume -= order_update.volume
            # 对于买单的成交回报
            with self.portfolio_lock:
                if order_update.direction == 'BUY':
                    # 调整策略现金，按订单价还原现金，按实际成交价扣减现金
                    self.portfolio[cur_strategy]['cash'] += self.system_order_list[cur_order_id].price * \
                                                            order_update.volume
                    self.portfolio[cur_strategy]['cash'] -= order_update.amount
                    # 调整仓位
                    if cur_inst_id in self.portfolio[cur_strategy].keys():
                        self.portfolio[cur_strategy][cur_inst_id]['unsellable_position'] += order_update.volume
                    else:
                        self.portfolio[cur_strategy][cur_inst_id] = {'inst_id': cur_inst_id,
                                                                     'unsellable_position': order_update.volume,
                                                                     'sellable_position': 0,
                                                                     'locked_position': 0}
                # 对于卖单的成交回报
                else:
                    # 调整现金
                    self.portfolio[cur_strategy]['cash'] += order_update.amount
                    # 调整仓位
                    self.portfolio[cur_strategy][cur_inst_id]['locked_position'] -= order_update.volume
        # 对于撤单回报
        elif order_update.update_type == 'CANCEL':
            with self.portfolio_lock:
                # 对于买单的撤单回报，返还现金
                if self.system_order_list[cur_order_id].direction == 'BUY':
                    self.portfolio[cur_strategy]['cash'] += self.system_order_list[cur_order_id].volume * \
                                                            self.system_order_list[cur_order_id].price
                # 对于卖单的撤单回报，解锁冻结股票
                else:
                    self.portfolio[cur_strategy][cur_inst_id]['sellable_position'] += order_update.volume
                    self.portfolio[cur_strategy][cur_inst_id]['locked_position'] -= order_update.volume
            # 最后清零订单中剩余的部分
            self.system_order_list[cur_order_id].volume = 0

        # # 如果订单volume清零，则删去该订单
        # if self.system_order_list[cur_order_id].volume == 0:
        #     del self.system_order_list[cur_order_id]

        # 告知策略成交回报
        self.strategy_manager.inform_order_update(order_update)

    # 主进程
    def run(self):
        self.public_context.log_message('Trader started.')
        while True:
            event = self.trader_queue.get()
            if event == 'SYSTEM_EXIT':
                break
            if event['type'] == 'order':
                self.execute_order(event['data'])
            elif event['type'] == 'order_update':
                self.process_order_update(event['data'])

        print('Trader exited.')
        self.public_context.log_message('Trader exited.')

    def finish(self):
        self.trader_queue.put('SYSTEM_EXIT')

    def print_orders(self):
        print('-> Current orders: ')
        if len(self.system_order_list):
            for k in self.system_order_list.keys():
                print('\t{}'.format(self.system_order_list[k]))
        else:
            print('\tNo order.')

    # 收盘结算
    def clearing(self):
        with self.portfolio_lock:
            for stg in self.portfolio:
                for inst_id in self.portfolio[stg]:
                    if inst_id == 'cash':
                        continue
                    cur_inst_track = self.portfolio[stg][inst_id]
                    cur_inst_track['sellable_position'] += cur_inst_track['unsellable_position'] + \
                                                           cur_inst_track['locked_position']
                    cur_inst_track['unsellable_position'] = 0
                    cur_inst_track['locked_position'] = 0

    # 获取当前策略市值
    def get_market_value(self, strategy=''):
        if strategy == '':
            result = {}
            for stg in self.portfolio.keys():
                result[stg] = self.get_market_value(stg)
            return result
        else:
            with self.portfolio_lock:
                stg_portfolio = self.portfolio[strategy]
                market_value = stg_portfolio['cash']
                for inst_id in self.portfolio[strategy].keys():
                    if inst_id == 'cash':
                        continue
                    cur_inst_track = self.portfolio[strategy][inst_id]
                    market_value += (cur_inst_track['sellable_position'] + cur_inst_track['unsellable_position'] +
                                     cur_inst_track['locked_position']) * self.public_context.get_market_price(inst_id)
            return market_value

    def get_position(self, strategy_name):
        with self.portfolio_lock:
            return self.portfolio[strategy_name]
